/*
 * Generate a header file with the macro prefix_FOREACH,
 * which iterates over the elements of a given
 * __VA_ARGS__, but see below.
 *
 * As until C++20, __VA_ARGS__ cannot be empty,
 * and the macro F(...) cannot be called with
 * 0 arguments, some cleverness is requireded to handle
 * empty loops.
 *
 * Driven by usage, the workaround picked here is
 * that the first argument of __VA_ARGS__ is
 * ignored by FOREACH, allowing the caller
 * to have an empty loop if needed.
 * This fits nicely with other use-cases,
 * where the caller has to merge `...` arguments
 * with the preceding argument, just to
 * satisfy the never empty requirement.
 *
 * The max_iteration argument is the maximum
 * number of times FOREACH calls the given
 * F macro.
 *
 * Different FOREACH implementations were
 * bechmarked. The solution below is
 *  + the fastest to compile
 *  + the easiest to understand
 *  + the easiest to extend
 *  - verbose, has to be generated.
 *
 * EXPAND is used to workaround MSVC bug:
 * __VA_ARGS__ is pasted as a single token.
 */

#include <cstdlib>
#include <iostream>

const char header_guard[] = "MSERIALIZE_DETAIL_FOREACH_HPP";
const char prefix[] = "MSERIALIZE_";

void write_count(std::ostream& out, int max_count)
{
  out << "\n/** Count the number of elements in the given __VA_ARGS__ */\n"
      << "#define " << prefix << "COUNT(...) " << prefix << "EXPAND(" << prefix << "COUNT_I(__VA_ARGS__";
  for (int i = max_count; i != 0; --i)
  {
    out << ',' << i;
  }
  out << ",x))\n"
      << "#define " << prefix << "COUNT_I(";
  for (int i = 0; i < max_count+1; ++i)
  {
    out << 'a' << i << ',';
  }
  out << "...) a" << max_count << "\n";
}

void write_foreach(std::ostream& out, int max_iteration)
{
  out << "\n/** For each elem `e`, *except the first* in __VA_ARGS__, call F(d,e) */\n"
      << "#define " << prefix << "FOREACH(F, d, ...) \\\n  "
      << prefix << "EXPAND(" << prefix << "CAT(" << prefix << "FOREACH_, " << prefix << "COUNT(__VA_ARGS__)) (F, d, __VA_ARGS__))\n";

  out << "\n/** FOREACH_n ignores the first argument, and calls F n-1 times */\n";
  for (int i = 1; i <= max_iteration+1; ++i)
  {
    out << "#define " << prefix << "FOREACH_" << i << "(F,d,_";
    for (int j = 1; j < i; ++j)
    {
      out << ",a" << j;
    }
    out << ')';
    for (int j = 1; j < i; ++j)
    {
      out << " F(d,a" << j << ")";
    }
    out << "\n";
  }
}

int main(int argc, const char* argv[])
{
  /** The max number of arguments FOREACH will call F */
  const int max_iteration = (argc >= 2) ? std::atoi(argv[1]) : 1024; // NOLINT

  std::cout << "#ifndef " << header_guard << "\n"
            << "#define " << header_guard << "\n\n";

  std::cout << "// Generated by GenerateForeachMacro " << max_iteration << "\n";

  write_count(std::cout, max_iteration+1);
  write_foreach(std::cout, max_iteration);

  std::cout << "\n#endif // " << header_guard << "\n";
}
